﻿//
//  server.cpp
//  TcpServer
//
//  Created by sandrial on 2017/9/20.
//  Copyright © 2017年 wzy. All rights reserved.
//

#define MULTITHREADS 1
#define DEBUG

#include "lib/json/json.h"

#include "server.hpp"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#include <sys/socket.h>
#include <pthread.h>
#else
#include <Windows.h>
#endif
#include <sys/types.h>
#include <string>
#include <algorithm>

//modes
#define SERVER_LOGIN 0  //登入
#define SERVER_LOGOUT 1    //登出
#define SERVER_USERSTREAM 2     //用户数据流
#define SERVER_ENDSTREAM 3  //结束数据流传输
#define OFFLINE 3 //离线

//user status
#define USER_LOGIN 0 //用户流传输允许_登录
#define USER_CLOSED 1 //用户流传输正常结束，关闭
#define USER_FORBIDDEN 3 //用户流传输强制关闭


//互斥锁userlock：一次只允许一个user对user表进行操作
#ifndef _WIN32
static pthread_mutex_t userlock;
#else
static HANDLE userlock;
#endif

void Dlog(const char *mes){
#ifdef DEBUG
#ifndef _WIN32
    write(fileno(stdout),mes,strlen(mes));
#else
	fwrite(mes,strlen(mes),sizeof(char),stdout);
#endif
#endif
}

/*
 * 回调函数：void callback
 * 参数：
 * int mode  消息处理模式，控制当前线程的工作方式
 * int mes   任意回调参数，与消息处理模式相关
 * 功能：
 *      处理服务器接受的信息，在EstablishClient管理器中进行了预处理，接收客户端传输的信息并分析消息处理模式，回调本函数进行所有情况的消息处理。对用户数据表操作时使用互斥锁，其他客户端无法对用户数据表进行操作。
 */
void Server::callback(int mode,void * mes){
    int len = (int)users.size();
    
    switch (mode) {
            
/********************LOGIN*********************/
            
        case SERVER_LOGIN:
        {//给每个用户分配一个单独的id
#ifndef _WIN32
            pthread_mutex_lock(&userlock);
#else
			WaitForSingleObject(userlock, INFINITE);
#endif
            int i,flag = 0, uskid = *(int*)mes;
            std::string str = "";
            
            for(i = 0; i < len; ++i){
                //存在被占用的socketid，错误
                if(users[i]._sid == uskid ){
                    users[i].status != USER_LOGIN?flag = 2:flag = 0;
                    if(users[i].status == 3){
                        //之前占用的socketid已断开
                        --flag;
                    }
                    break;
                }
            }
            //允许用户登录
            if(flag < 2){
                users.push_back(UserInf(i,uskid,USER_LOGIN));//i为服务器和客户端的内置id
                str += "Acc:";
                str += i+'0';//分配i
            }
            else{
                str += "Rej";//拒绝
            }
#ifndef _WIN32
            pthread_mutex_unlock(&userlock);
#else
			ReleaseMutex(userlock);
#endif            
            if(send(uskid, str.c_str(), str.size(), NULL) <= 0){
                Dlog("Send error!\n");
            }
            
        }
            break;
            
            
/********************LOGOUT*********************/
            
        case SERVER_LOGOUT:
        {
#ifndef _WIN32
			pthread_mutex_lock(&userlock);
#else
			WaitForSingleObject(userlock, INFINITE);
#endif
            int i,flag = 0;
            std::string str = "";
            
            for(i = 0; i < len; ++i){
                //存在被占用的socketid，成功
                if(users[i]._sid == *(int *)mes){
                    flag = 1;
                    break;
                }
            }
            
            if(flag){
                users[i].status = OFFLINE;
                str += "Acc";
            }else{
                str += "Rej";
            }
#ifndef _WIN32
			pthread_mutex_unlock(&userlock);
#else
			ReleaseMutex(userlock);
#endif
            
            send(*(int *)mes,str.c_str(),str.size(),NULL);
            //TODO:数据库用户状态清理
            
        }
            break;
            
            
            
/********************STREAM*********************/
            
        case SERVER_USERSTREAM:
            //用户传入数据
        {
            Json::Value j;
            Json::Reader reader;
            userStream* stream = (userStream*) mes;
            
            if(users.size() <= stream->id){
                break;
            }
            switch(users[stream->id].status){
                case USER_LOGIN:
                {
					//TODO: 读取用户名，密码，查询数据库
                    std::string user,passwd;
                    reader.parse(stream->stream, j);
                    
                    user = j["username"].asString();
                    passwd = j["password"].asString();

                    if(login(user,passwd)){
                        send(users[stream->id]._sid,"Acc",4,0);
                    }else{
                        send(users[stream->id]._sid,"Rej",4,0);
                    }
                }
                    break;
                default:
                    //错误的status，不允许传输数据
                    send(users[stream->id]._sid,"Rej",4,0);
                    break;
            }
        }
            break;
            
/********************ENDSTREAM*********************/
            
        case SERVER_ENDSTREAM:
            
            break;
        case -1:
            printf("error");
            break;
        default:
            break;
    }
}

void Server::server_start(){
    int t;
    int connect_fd;
    struct sockaddr_in servaddr;
#if MULTITHREADS
#ifndef _WIN32
	pthread_t th;
#else
	HANDLE th;
#endif
#endif
    fd_set set;
    int n;
    timeval inputtime{0,0},time{1,0};

    
    char command[40];//服务器端可接收命令
    
    //初始化socket
    if((_sid = socket(AF_INET,SOCK_STREAM,0))== -1)
    {
        exit(0);
    }
    
    memset(&servaddr,0,sizeof(sockaddr_in));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//自动获取本机IP
    servaddr.sin_port = htons(DEFAULT_PORT);
    
    //bind()函数
    if((t = bind(_sid,(struct sockaddr*)&servaddr,sizeof(servaddr)))==-1)
    {
        printf("绑定失败:%d\n",t);
        exit(0);
    }
    
    //listen()
    if(listen(_sid,10) == -1){
        printf("监听失败");
        exit(0);
    }
    
    printf("开启监听...\n");
    //用户console命令：每秒接收/发送，exit命令退出服务器
    while(strcmp(command,"exit\n")!=0){
        FD_ZERO(&set);
        FD_SET(fileno(stdin),&set);
        
        if(select(1, &set, NULL, NULL, &inputtime)> 0){
#ifndef _WIN32
            n = (int)read(fileno(stdin), command, 40);
#else
			n = fread(command, 40, sizeof(char), stdin);
#endif
            if(n > 0)
                command[n] = '\0';
            printf("%s\n",command);
        }
        
        FD_ZERO(&set);
        FD_SET(_sid,&set);
        if(select(300,&set,NULL,NULL,&time)>0){
            //没有客户端连接时，accpet()函数会一直阻塞
            if((connect_fd = (int)accept(_sid, (struct sockaddr*)NULL,NULL)) == -1){
                printf("接收失败\n");
                continue;
            }
            //对于每个用户开启进程，子进程中处理消息
			threadmes *mes = new threadmes();
            
			mes->s = this;
			mes->connect_fd = connect_fd;
#ifndef _WIN32
#if MULTITHREADS
            pthread_create(&th, NULL, MessageIn, (void*)mes);
#else//用于测试..
            Server::MessageIn((void *)mes);//单线程，单进程阻塞
#endif
#else
            //windows
#if MULTITHREADS
			th = CreateThread(NULL, 0, Server::EstablishClient, (void *)mes, 0, 0);
#else//用于测试..
			Server::MessageIn((void *)mes);//单线程，单进程阻塞
#endif
#endif
        }
    }

}

/*
 * 方法run
 * 初始化，服务器流程开始（通过修改Multitread宏可调整单线程(Debug)/多线程版本服务器）。
 * 服务器通过使用者在控制台输入“exit”命令关闭
 * 初始化互斥锁，连接数据库，开始监听。关闭服务器时关闭与数据库的连接，关闭互斥锁。
 * 返回值：-1初始化错误, 0运行正常结束。
 */
int Server::run(){
    //初始化互斥锁
#ifndef _WIN32
    pthread_mutex_init(&userlock, NULL);
#else
	userlock = CreateMutex(nullptr, FALSE, nullptr);
#endif
    
    //初始化数据库
    if(_db.conndb()!=0)
        return -1;
    Dlog("Connected to database, starting server...\n");
    server_start();
    
    //关闭互斥锁
#ifndef _WIN32
    pthread_mutex_destroy(&userlock);
#else
	CloseHandle(userlock);
#endif
    _db.disconndb();
    return 0;
}


/*
 静态方法 EstablishClient
 参数：tmes 线程参数
 功能：
 * 客户端通信Manager，每个用户唯一对应一个EstablishClient函数。
 * 当服务器监听到客户端传送请求时，执行回调，将客户端连接scoketid传入，进行消息处理。
 * 首先读取消息，预处理识别消息类型，最后交给callback。
 */
#ifndef _WIN32
void *Server::MessageIn(void * tmes){
#else
DWORD WINAPI Server::EstablishClient(void* tmes){
#endif
    using namespace std;
    
    long n;
    int mode = -1;
    char buff[MAXSIZE+1];
    threadmes *tm = (threadmes *)tmes;
    userStream stream;
    string mes;
    
	while (1) {
		//读取消息，阻塞，和client保持同步
		n = recv(tm->connect_fd, buff, MAXSIZE, 0);
		if (n < 0) {
			//客户端非正常退出，会接收一个空消息
			mode = SERVER_LOGOUT;
			tm->s->callback(mode, (void *)&(tm->connect_fd));

			string a = "Client:";
			a += 1;
			a += " logout.\n";
			Dlog(a.c_str());

			break;
		}
		buff[n] = '\0';
		mes = string(buff);

		Dlog("MessageIn:");
		//检查消息
		if (mes == "login") {//登录
			mode = SERVER_LOGIN;
			Dlog("login\n");
			tm->s->callback(mode, (void *)&(tm->connect_fd));
		}
		else if (mes == "logout") {//登出
			mode = SERVER_LOGOUT;
			Dlog("logout\n");
			tm->s->callback(mode, (void *)&(tm->connect_fd));
			break;
		}
		else if (mes == "text") {//文字

		}
		else if (mes == "audio") {//语音通话

		}
		else if (mes == "video") {//视频通话

		}
		else if (mes == "file") {//文件

		}
		else if (mes == "friend") {//添加好友

		}
		else if (mes == "blacklist") {//黑名单

		}
		else if (mes.find("endstream") != string::npos) {//结束本次流传输
			Dlog("endstream\n");
		}
		else {
			/*
			* 数据流传输			
			* 发送数据格式：id@n:data
			* 结束数据格式：id@endstream:N
			* id:服务器给客户端分配的id
			* n:当前包
			* N:包总数
			*/
			Dlog("stream\n");
			//获取用户id
			string idstr = mes.substr(0, mes.find_first_of('@'));
			//设置userStream
			stream.id = atoi(idstr.c_str());
			stream.stream = mes.substr(mes.find_first_of(':')+1);
			mode = SERVER_USERSTREAM;
			tm->s->callback(mode, (void *)&stream);
		}
	}
    //清理内存
    delete tm;
    
    //线程退出
#if MULTITHREADS
#ifndef _WIN32 //WIN32不需要任何exit方法，return 即可
    pthread_exit(NULL);
#endif
#endif
    return 0;
}

//函数：login
//登录，从db中验证用户名和密码
int Server::login(std::string &user, std::string &password){
    return _db.dbLogin(user, password);
}
